/**
  * Operations gets the processed commands from userCommand class and 
  * further execute each of the command by calling respective functions. 
  * It will not have any attributes that related to storage

  * The major operations are: 
  * 1. add an event to the storage
  * 2. delete an event or many events based on the task IDs
  * 3. edit an event's attributes
  * 4. display all events or certain events
  * 5. events will be saved to the file when users exit the program
  * 6. undo the past operations
  * 7. sort events in terms of the deadline
  * 8. find help information for operations
  * 9. change the setting of the TaskExemplar
  
  The following operations will be implemented in the future

  * 10. block multiple dates for an event

  *coded by Deng Xuan
  */

#pragma once

#include "CommonDataType.h"
#include "OperationType.h"
#include <vector>
#include "UserCommand.h"
#include "EventStorage.h"
#include "ActionList.h"
#include "ConstantList.h"
#include "IndexTranslator.h"
#include "OutputDisplay.h"
#include "TECommonFunctions.h"
#include <algorithm>
#include <cmath>

using std::vector;
using namespace SearchConstants;
using std::abs;

class MyTime:public Time {
public:
	Month gotoNextMonth ();
	void operator = (const Time& rightOperand);
	bool operator == (const MyTime& rightOperand); 
	MyTime (const Time& theOtherTime);

};

class Operations {

public: 
	~Operations();
	static Operations& get();
    void initializeOperations();
	void executeCommand(UserCommand& aCommand);
	void executeUndo(UserCommand& aCommand);
	//void getDisplayTextPointer(struct DisplayText* displayTextPointer);//n
	void readFromStorage ();

private:
	IndexTranslator _availableData;
	EventVector* _eventList;   
	//static struct DisplayText* displayText;//n
	static bool hasRun; 
	Operations ();
	Operations (const Operations&);
	Operations& operator = (const Operations&);
	void performGeneralSearch (const SearchStruct& searchInfo); 
	void performSpecificSearch (const SearchFilter& keyword, vector<pos_int> *availableID);

	void addEvent (Event anEvent);
	void markEvent (pos_int ID); 
	void deleteByNumber (const vector<pos_int> & IDs);
	void deleteByEvent (Event anEvent);
	void displayEvent ();
	void sortEvent ();
	void configuration ();
	void undoCommand ();
	//void redoCommand();
	void modifyEvent (pos_int ID);
	void blockDates ();
	void saveToFile ();
	void loadFromFile ();
	//void clearHistoryLists();
	bool isDeleteTwice (vector<pos_int> IDs);
	bool isDeleteValid (vector<pos_int> IDs);
	bool isUndoValid ();

	//static void printOnScreen(OperationType command);
	//void toString();
};

typedef Operations * const WriteOperationPtr;
typedef Operations const * const ReadOperationPtr; 

template <typename T> static void foundMatch (vector<T> keywords, vector<T> targets,
	pos_int &count, pos_int &distance){
	int lastPos = -1;
	for (int i = 0; i< (int)keywords.size(); i++) {
		for (int j = 0; j< (int)targets.size(); j++ ){
			if (keywords[i] == targets[j]) {
				(count) ++;
				if (lastPos >= 0) {
					(distance) += abs(j - lastPos) - 1;
				}
				lastPos = j;
				break;
			}
		}
	}
};
				

static void checkDetailsMet (const Event &anEvent, con_str &details, pos_int &match, pos_int &distance){

	string targetString = ignoreCase (anEvent.getDetails());
	string keywordsString = ignoreCase (details);
	vector<string> targetStringList;
	vector<string> keywordsStringList; 
	vector<pos_int> indicesList;

	splitString (
		keywordsString,
		SEARCH_SEP_CHAR_LIST, 
		SEARCH_SEP_LIST_CHAR_N, 
		keywordsStringList,
		indicesList
	);
	
	splitString (
		targetString, 
		SEARCH_SEP_CHAR_LIST, 
		SEARCH_SEP_LIST_CHAR_N, 
		targetStringList,
		indicesList
	);
	
	foundMatch<string>(keywordsStringList, targetStringList, match, distance);
};


static void checkTagMet (Event &anEvent, const vector<string> &tags, pos_int &match, pos_int &distance){

	vector<string> *ptr = anEvent.getTagPtr();

	foundMatch<string> (tags, *ptr, match, distance);
	
};

static void checkTimeMet (const Event &anEvent,const Time& startTime, const Time& endTime
					   , pos_int &match, pos_int &distance){


	vector<MyTime> rangeOfTargetTime;
	vector<MyTime> rangeOfKeywordsTime;

	RepeatFrequency frequency = anEvent.getRepeatRule().frequency;
	
	MyTime targetTemp = anEvent.getStartTime();
	MyTime keywordsTemp = startTime;

	rangeOfKeywordsTime.push_back (startTime);
 	keywordsTemp++;
	while (keywordsTemp <= endTime) {
		rangeOfKeywordsTime.push_back(keywordsTemp);
		keywordsTemp++;
	}

	switch(frequency) {
	case RepeatFrequency::NO_REPEAT:
		rangeOfTargetTime.push_back(anEvent.getStartTime());
		if (anEvent.isDuration() == true) {
			targetTemp++;
			while (targetTemp <= anEvent.getEndTime()) {
				rangeOfTargetTime.push_back (targetTemp);
				targetTemp++;
			}
		}
		break;
	
	case RepeatFrequency::EVERY_N_DAY:
		do{
			rangeOfTargetTime.push_back(targetTemp);
			if (anEvent.isDuration() == true){
				targetTemp++;
				MyTime temp = targetTemp;
				while (temp <= anEvent.getEndTime()) {
					rangeOfTargetTime.push_back(temp);
					temp++;
				}
			}

			for (pos_int i=0; i<anEvent.getRepeatRule().period; i++){
				targetTemp ++;
			}

		}while (targetTemp <= endTime);
		break;

	case RepeatFrequency::EVERY_MONTH:
		do {
			rangeOfTargetTime.push_back(targetTemp);

			if (anEvent.isDuration() == true){
				targetTemp++;
				MyTime temp = targetTemp;
				while (temp <= anEvent.getEndTime()) {
					rangeOfTargetTime.push_back(temp);
					temp++;
				}
			}
			targetTemp.gotoNextMonth();
		}while (targetTemp <= endTime);
		break;

	default:
		break;
	}

	
	pos_int tempMatch = 0;
	pos_int temp = distance;
	foundMatch<MyTime> (rangeOfKeywordsTime,rangeOfTargetTime, tempMatch, distance);
	
	distance = temp;
	if (match == 0){
		distance ++;
	}
	match = match + tempMatch;
	
	
};

static bool isFiltersMet(const Event &anEvent, vector<SearchFilter> filters) {
	for (auto i: filters){
		switch(i){
		case SearchFilter::DONE:
			if (anEvent.getIsDone() == true){
				return true;
			}
			break;

		case SearchFilter::NOT_DONE:
			if (anEvent.getIsDone() == false){
				return true;
			}
			break;
	
		case SearchFilter::REMINDED:
			if (anEvent.getIsReminded() == true){
				return true;
			}
			break;

		case SearchFilter::NOT_REMINDED:
			if (anEvent.getIsReminded() == false){
				return true;
			}
			break;

		case SearchFilter::FLOATING:
			if (anEvent.isFloating() == true){
				return true;
			}
			break;

		case SearchFilter::NOT_FLOATING:
			if (anEvent.isFloating() == false){
				return true;
			}
			break;
		
		default:
			failProgram (ERR_Operations::invalid_search_filter);
			break;
		}
		return false;
	}
};

static bool compareByRelevance (const SearchRelevance &a, const SearchRelevance &b){
	return a.relevanceIndex > b.relevanceIndex ; 
};

static void setFoundEvents (vector<SearchRelevance> &foundEvents, pos_int index, pos_int &match, pos_int &distance){
	SearchRelevance temp; 
	temp.storageIndex = index;
	temp.relevanceIndex = match - distance;
	foundEvents.push_back(temp);
}